# coding=utf-8
from collections import deque
from datetime import datetime
from flask_restful import Resource
from dateutil.relativedelta import relativedelta
from financial.resorces.transaction.const import DealType
from comment.ultils.decorators import Login_required
from comment.modules import db
from comment.modules.matched_result import Matched_result
from comment.modules.expected_return import Expected_return
from comment.modules.debt_info import Debt_info
from comment.modules.funding_not_matched import Funding_not_matched
from  sqlalchemy import or_,and_ #两个条件之间的组合
from flask import current_app

from comment.ultils.gen_trans_id import gen_trans_id


class MathQueue:
    '''
    采用deque来构建一个栈
    '''
    def __init__(self,item_list):
        '''初始化栈'''
        self.items = deque(item_list)

    #从栈取一条待匹配的数据
    def get_item(self):
        return self.items.popleft() #从左端取数据

    #将一个即将待匹配的数据放入栈中
    def push(self,item):
        return self.items.appendleft(item) #appendleft把数据放在左边

    #判断栈是否为空
    @property
    def empty(self):
        return len(self.items) == 0

class Match(object):

    '''拦截器看用户有没有登录过'''
    # 拦截器他会在LoginOut的所有的视图函数都会在执行视图函数之前，做一下装饰器的拦截
    method_decorators = [Login_required]  # 使用了登录拦截的装饰器
    '''
    正式开始撮合匹配资金
    '''

    def __init__(self):
        '''一共有两个栈'''
        '''初始化时，把所有待匹配的债权和待匹配的资金都从数据库中取出来'''
        #查询未匹配或者待匹配的债权列表
        debt_list = Debt_info.query.filter(or_(Debt_info.matchedStatus == 0,Debt_info.matchedStatus == 1)).order_by(Debt_info.id).all()
        #把他们放到栈中（待匹配的债权栈）
        self.debtDeque = MathQueue(debt_list) #谁先产生债权就谁先匹配

        #未匹配或者部分匹配资金列表
        fund_list = Funding_not_matched.query.filter(or_(Funding_not_matched.matchedStatus == 0,Funding_not_matched.matchedStatus == 1)).order_by(Funding_not_matched.fId).all()
        # 把他们放到栈中（待匹配的资金栈）
        self.fundsDeque = MathQueue(fund_list)

    def start_match(self):
        '''正式开始撮合匹配'''
        try:
            while (not self.fundsDeque.empty) and (not self.debtDeque.empty):
                debt_item = self.debtDeque.get_item()
                fund_item = self.fundsDeque.get_item()
                #匹配有三种情况，第一：债权资金大于理财金额  第二：债权资金小于理财金额  第三：债权资金等于理财金额
                if debt_item.debtMoney > fund_item.fNotMatchedMoney:
                    self.debtMoneyMoreThanFunds(debt_item,fund_item)
                elif debt_item.debtMoney < fund_item.fNotMatchedMoney:
                    self.debtMoneyLessFunds(debt_item,fund_item)
                else:
                    self.debtMoneyEquaFunds(debt_item,fund_item)
                db.session.commit()
                return {'msg':'匹配完成'}
        except Exception as e:
            current_app.logger.error(e)
            db.session.rollback()
            return {
                'message':'服务器忙，请稍后重试',
                'code':217
            }

    # 表示债权资金部分匹配的业务逻辑（债权资金大于理财金额）
    def debtMoneyMoreThanFunds(self,debt,funds):
        #新增一条匹配结果
        self.createMatchedResult(debt,funds,funds.fNotMatchedMoney)
        debt.debtMoney -= funds.fNotMatchedMoney #债权未匹配的金额 = 债权的资金 - 投资的资金
        debt.matchedStatus = 1 #修改匹配状态为1
        debt.matchedMoney += funds.fNotMatchedMoney #修改债权的已经匹配金额

        funds.fNotMatchedMoney = 0
        funds.matchedStatus = 2 #修改债权的匹配状态为2

        #债权还剩下一点资金没有匹配，马上重新插入栈中
        self.debtDeque.push(debt)

        #投资理财的金额全部匹配完了，所以可以开始产生收益了
        self.createExpectedResult(funds)

    def createMatchedResult(self,debt,funds,match_money):
        #生成流水号
        match_num = gen_trans_id(DealType.match.name)

        #创建一条匹配结果数据，并保存到数据库中
        #userId在这里是债权>投资金额，对于投资人来说已经是完全匹配，但对于借款人来说还没产生一条匹配结果，投资人已经产生了，所以传的是投资人的id，
        #debtId传的是债权的id
        result = Matched_result(userId=funds.investRecord.pUid, debtId=debt.id, investId=funds.investRecord.pId,
                                transferSerialNo=match_num, purchaseMoney=match_money, #purchaseMoney传的是匹配资金，谁的资金匹配完
                                confirmedDate=funds.investRecord.pDate,
                                isConfirmed=1, matchDate=datetime.now())

        db.session.add(result)

    def createExpectedResult(self,funds):
        ex_return = Expected_return(userId=funds.investRecord.pUid, productId=funds.investRecord.pProductId,
                                    investRecord=funds.investRecord.pId,
                                    expectedDate=datetime.now() + relativedelta(months=funds.investRecord.pDeadline),
                                    expectedMoney=funds.investRecord.pProspectiveEarnings)
        funds.investRecord.pStatus = 1 #修改状态，表示已经产生收益了
        #修改投资记录的开始计息时间
        funds.investRecord.pInterestStartDate = datetime.now()
        db.session.add(ex_return)

    #表示投资资金部分匹配的业务逻辑（债权资金小于理财金额）（债权全部匹配完成）
    def debtMoneyLessFunds(self,debt,funds):
        funds.fNotMatchedMoney -= debt.debtMoney #投资资金剩余未匹配的金额 = 理财金额 - 债权金额
        funds.matchedStatus = 1 #代表部分匹配（上面的是债权的金额大于投资的金额，投资金额可以全部完成，现在的投资金额只能进行部分匹配）
        self.createMatchedResult(debt,funds,debt.debtMoney) #创建一条匹配的结果
        debt.matchedMoney += debt.debtMoney #修改债权已经匹配的金额
        debt.debtMoney = 0 #匹配完成之后，修改债权的待匹配金额
        debt.user.accountInfo.balance += debt.repaymenMoney
        debt.matchedStatus = 2
        self.startRepayPlay(debt) #正式开始还款计划
        self.fundsDeque.push(funds) #把剩余没有匹配的资金放入栈中

    def startRepayPlay(self,debt):
        '''正式生效还款计划'''
        repay_list = debt.debtor_record
        for i in range(len(repay_list)):
            repay_data = datetime.now() + relativedelta(months=i+1)
            #修改还款计划中的还款时间
            repay_list[i].receivableDate = repay_data

    #债权资金等于理财金额
    def debtMoneyEquaFunds(self,debt,funds):
        debt.matchedStatus = 2 #债权匹配的状态 0未匹配 1部分匹配 2完全匹配
        debt.matchedMoney += debt.debtMoney #修改债权中已经匹配的金额
        self.createMatchedResult(debt,funds,debt.debtMoney) #因为相等，所以传哪个都行
        debt.debtMoney = 0 #金额已经全部匹配完成
        funds.fNotMatchedMoney = 0 #待匹配的投资金额
        funds.matchedStatus = 2
        self.createExpectedResult(funds)
        #因为已经完全匹配，所以不用往栈中加数据了
        self.startRepayPlay(debt) #同时开始还款计划，同时开始收益

#启动项目
class MatchUp(Resource):
    def post(self):
        match = Match()
        return match.start_match()

if __name__ == '__main__':
    pass

